#include <sys/socket.h>
#include <sys/types.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <math.h>
#include "Frame.h"

//the number of bits to shift when generating B(x)
#define extra 7
#define genPoly 227

using namespace std;

Frame generateDataFrame(int sender, int destination, int messagesLeft);
Frame generateNAKFrame(int sender, int destination);
Frame generateACKFrame(int sender, int destination);
bool validDataFrame(Frame received);

int main(int argc, char **argv)
{

    //make sure both command line arguments are present
    if(argc != 3)
    {
        perror("Improper usage of command. Correct format: 'a.out n m' where n and m are integer values to generate random numbers");
        exit(1);
    }

    pid_t child_pid;
    //next 3 variables are used to receive messages between processes
    int bufferFromP1toP2[1] = {0};
    Frame frameWrap[1], lastSent;
    
    bool sendData, waitForAck, receivedNAK = false, otherProcessFinished = false, createdNak = false;

    //socket creation for IPC between processes
    int socketFromP1toP2[2], data;

    int amountOfRandNumsToGenerateFromParent = atoi(argv[1]);
    int amountOfRandNumsToGenerateFromChildOne = atoi(argv[2]);

    //create socketpairs between processes
    if(socketpair(AF_UNIX, SOCK_STREAM, 0, socketFromP1toP2) < 0)
    {
        perror("socket creation from P1 to P2 failed\n");
        exit(1);
    }

    child_pid = fork();

    if(child_pid == 0)
    {
        //close one end of the socket from P1 and P0
        close(socketFromP1toP2[1]);
	
	sendData = true;
	waitForAck = false;   
	
	//seed random number generator
	srand(time(NULL)); 
	
	lastSent = frameWrap[0] = generateDataFrame(1, 0,amountOfRandNumsToGenerateFromChildOne);
	
        //loop until P0 has finsihed and until there are no more messages to send to P0
        while(amountOfRandNumsToGenerateFromChildOne > -1 || !otherProcessFinished)
        {
	    if(sendData)
	    {
		//if process if finished and it hasn't received a NAK nor sent a NAK, then create the final
		//frame to let other process know it is finsihed. this process will only send ACK frames once this
		//final frame has sent. 
		if(amountOfRandNumsToGenerateFromChildOne == 0 && !receivedNAK && !createdNak)
		{
		    lastSent = frameWrap[0] = generateDataFrame(1,0, amountOfRandNumsToGenerateFromChildOne);
		    frameWrap[0].setMessage("-1");
		}

		//send data frame, however, if a NAK was received, resend the previous data frame, else
		//send the newly created frame
		if(frameWrap[0].getFrameType() == Frame::DATA_FRAME)
		{
		    if(receivedNAK)
		    {
			printf("Resending frame from Process 1 to Process 0, with message %s\n", frameWrap[0].getMessage());
			receivedNAK = false;
		    }
		    else
		    {
			printf("Sending frame from Process 1 to Process 0, with message %s\n", frameWrap[0].getMessage());
			amountOfRandNumsToGenerateFromChildOne--;
		    }
		}

		write(socketFromP1toP2[0], frameWrap, sizeof(Frame));
		sendData = false;
	    }
	    else //receiving data
	    {
		read(socketFromP1toP2[0], frameWrap, sizeof(Frame));

		//if a data frame has been received, there are two possible ways of processing. firstly, if the message is -1
		//it means the other process has finished sending all of its' messages, else, the frame needs to be validated
		//to check for errors during transmission. If an error occurred, generate a NAK frame, and send, else, send an
		//ACK frame.
		if(frameWrap[0].getFrameType() == Frame::DATA_FRAME)
		{
		    receivedNAK = false;
		    if(atoi(frameWrap[0].getMessage()) == -1)
		    {
			printf("Process 0 finished\n");
			otherProcessFinished = true;
			

			if(amountOfRandNumsToGenerateFromChildOne <=0)
			{
			    frameWrap[0] = generateACKFrame(1,0);
			}
			else
			{
			    lastSent = frameWrap[0] = generateDataFrame(1,0, amountOfRandNumsToGenerateFromChildOne);
			}

			if(amountOfRandNumsToGenerateFromChildOne >= -1)
			{
			    sendData = true;
			}
		    }
		    else
		    {
			if(validDataFrame(frameWrap[0]))
			{
			    printf("Process 1 received a message of %s from Process 0\n", frameWrap[0].getMessage());

			    if(amountOfRandNumsToGenerateFromChildOne <=0)
			    {
				frameWrap[0] = generateACKFrame(1,0);
			    }
			    else
			    {
				lastSent = frameWrap[0] = generateDataFrame(1,0, amountOfRandNumsToGenerateFromChildOne);
			    }

			    createdNak = false;
			}
			else
			{
			    printf("Process 1 received a damaged frame from Process 0\n");
			    frameWrap[0] = generateNAKFrame(1,0);
			    createdNak = true;
			}

			if(amountOfRandNumsToGenerateFromChildOne >= -1)
			{
			    sendData = true;
			}
		    }
		}
		else //received an ACK_FRAME, ACK_FRAME can be either an ACK or a NAK
		{
		    //if a NAK was received, resend last Frame, decrementing the FCS by one
		    //to simulate a damaged frame, the FCS was incremented by one, so this decrement
		    //will return the FCS to its correct value
		    if(atoi(frameWrap[0].getMessage()) == Frame::NAK)
		    {
			sendData = true;
			receivedNAK = true;
			data = lastSent.getFCS();
			data++;
			lastSent.setFCS(data);
			frameWrap[0] = lastSent;
		    }
		    else
		    {
			if(amountOfRandNumsToGenerateFromChildOne <=0)
			{
			    frameWrap[0] = generateACKFrame(1,0);
			}
			else
			{
			    lastSent = frameWrap[0] = generateDataFrame(1,0, amountOfRandNumsToGenerateFromChildOne);
			}

			sendData = true;
		    }
		}
	    }
	}

	close(socketFromP1toP2[0]);
        
        exit(0);
    }
    else
    { // Parent process
    
	sendData = false;
	waitForAck = false;

	close(socketFromP1toP2[0]);

	//seed random number generator
	srand(time(NULL) * 3 / 6);
       
        //Parent process (P0) should generate n random numbers depending on command line argument
        while(amountOfRandNumsToGenerateFromParent > -1 || !otherProcessFinished)
        {
	    if(sendData)
	    {
		if(amountOfRandNumsToGenerateFromParent == 0 && !receivedNAK && !createdNak)
		{
		    lastSent = frameWrap[0] = generateDataFrame(1,0,amountOfRandNumsToGenerateFromParent);
		    frameWrap[0].setMessage("-1");
		}
		if(frameWrap[0].getFrameType() == Frame::DATA_FRAME)
		{
		    if(receivedNAK)
		    {
			printf("Resending frame from Process 0 to Process 1, with message %s\n", frameWrap[0].getMessage());
			receivedNAK = false;
		    }
		    else
		    {
			printf("Sending frame from Process 0 to Process 1, with message %s\n", frameWrap[0].getMessage());
			amountOfRandNumsToGenerateFromParent--;
		    }
		}

		write(socketFromP1toP2[1], frameWrap, sizeof(Frame));
		sendData = false;
	    }
	    else
	    {
		read(socketFromP1toP2[1], frameWrap, sizeof(Frame));

		if(frameWrap[0].getFrameType() == Frame::DATA_FRAME)
		{
		    receivedNAK = false;
		    if(atoi(frameWrap[0].getMessage()) == -1)
		    {
			printf("Process 1 finished\n");
			
			otherProcessFinished = true;

			if(amountOfRandNumsToGenerateFromParent <=0)
			{
			    frameWrap[0] = generateACKFrame(0,1);
			}
			else
			{
			    lastSent = frameWrap[0] = generateDataFrame(0,1,amountOfRandNumsToGenerateFromParent);
			}

			if(amountOfRandNumsToGenerateFromParent >= -1)
			{
			    sendData = true;
			}
		    }
		    else
		    {
			if(validDataFrame(frameWrap[0]))
			{
			    printf("Process 0 received a message of %s from Process 1\n", frameWrap[0].getMessage());

			    if(amountOfRandNumsToGenerateFromParent <=0)
			    {
				frameWrap[0] = generateACKFrame(0,1);
			    }
			    else
			    {
				lastSent = frameWrap[0] = generateDataFrame(0,1,amountOfRandNumsToGenerateFromParent);
			    }

			    createdNak = false;
			}
			else
			{
			    printf("Process 0 received a damaged frame from Process 1\n");
			    frameWrap[0] = generateNAKFrame(0,1);
			    createdNak = true;
			}

			if(amountOfRandNumsToGenerateFromParent >= -1)
			{
			    sendData = true;
			}
		    }
		}
		else
		{
		    if(atoi(frameWrap[0].getMessage()) == Frame::NAK)
		    {
			sendData = true;
			receivedNAK = true;
			data = lastSent.getFCS();
			data++;
			lastSent.setFCS(data);
			frameWrap[0] = lastSent;
		    }
		    else
		    {
			if(amountOfRandNumsToGenerateFromParent <=0)
			{
			    frameWrap[0] = generateACKFrame(0,1);
			}
			else
			{
			    lastSent = frameWrap[0] = generateDataFrame(0,1,amountOfRandNumsToGenerateFromParent);
			}

			sendData = true;
		    }
		}
	    }								      
        }        

	close(socketFromP1toP2[1]);
        
        //wait for child process to finish before exiting.
        wait(0);
        cout << "Oh no! my child has died!\n";
        
        return 0;
    }
}

Frame generateDataFrame(int sender, int destination, int messagesLeft)
{
    int data, dataString, remainder, transmission;
    Frame toSend;
    int temp;
    
    data = 1 + rand() % 1000;
    
    //bit shift the data to add extra zeros
    dataString = data << extra;
    remainder = dataString % genPoly; 

    //make the last frame to send corrupt
    if(messagesLeft == 1)
    {
	remainder--;
    }
   
    
    toSend = Frame(sender, destination, true,data, remainder);
    
    return toSend;
}

Frame generateNAKFrame(int sender, int destination)
{
    return Frame(sender, destination, false, -1, 0);
}

Frame generateACKFrame(int sender, int destination)
{
    return Frame(sender, destination, false, 1001, 0);
}

bool validDataFrame(Frame received)
{
    int data, remainder;

    data = atoi(received.getMessage());
    remainder = (data << extra) % genPoly;    

    if(remainder == received.getFCS())
    {
	return true;
    }
    else
    {
	return false;
    }
}